/*
 * sleep-inhibit.c
 * 
 * terminal app to inhibit power saving on OS X.
 *
 * Adapted from code in http://developer.apple.com/library/mac/#qa/qa2004/qa1340.html
 *
 * Copyright (C) 2010 Gordon Harris.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 2 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
 * 02111-1307, USA.
 *
 *
 */


#include <CoreServices/CoreServices.h>
#import <IOKit/pwr_mgt/IOPMLib.h>

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <unistd.h>
#include <time.h>
#include <sys/stat.h>

#include "sleep-inhibit.h"


#define DEBUG


void DispSyntax( LPAPPDATA lpAppData ) {
	
	printf("%s version %s syntax:\n\n", lpAppData->szAppName, lpAppData->szAppVersion );
	
	printf("%s NUMBER[SUFFIX]...\n\n", lpAppData->szAppName);
	printf("  Inhibit system idle sleep for NUMBER seconds.\n"
		   "  SUFFIX may be \'s\' for seconds (the default), \'m\' for minutes,\n"
		   "  \'h\' for hours or \'d\' for days.  NUMBER may be an arbitrary floating\n"
		   "  point number.\n\n");
	
	printf(	"Options include:\n"
		   "  -h  --help            Displays syntax / help.\n"
		   "  -v  --version         Displays version info.\n"
		   "  -t  --type i|d        Type of inhibit request, any combination of:\n"
		   "                        i (no session idle); d (no display idle).\n"
		   "                        The default is no session idle, i.e. --type i.\n"
		   "  -r  --reason \'REASON\' Reason for inhibiting sleep (default is 'not_idle').\n"
		   "  -q  --quiet           Don\'t print messages to the terminal.\n"
		   "  -d  --debug           Verbose messages.\n"
		   "  -s  --save            Save NUMBER to ~/.sleep-inhibit\n"
		   "  -l  --log [LOGFILE]   Copy messages to LOGFILE (default ==\n"
		   "                        /var/log/sleep-inhibit.log).\n"
		   "  -u  --utc --gmt       Timestamps in log as UTC rather than local time.\n"
		   "\n\n");
}


void DispVersion( LPAPPDATA lpAppData ) {
	printf("%s version %s, built %s.\n", lpAppData->szAppName, lpAppData->szAppVersion, __TIMESTAMP__);
}

char *GetTimeStr(LPAPPDATA lpAppData) {
	static char szTimeStr[80];
	time_t rawtime;
	struct tm * timeinfo;
	
	bzero(szTimeStr, 80);
	
	time ( &rawtime );
	
	if (lpAppData->uOpts & OPT_UTC)
		timeinfo = gmtime( &rawtime );
	else
		timeinfo = localtime ( &rawtime );
	
	//strftime (szTimeStr,80,"%c",timeinfo);
	strftime (szTimeStr,80,"%Y%m%d-%H%M%S",timeinfo);
	
	return szTimeStr;
}



void LogMessage (LPAPPDATA lpAppData, char *szFormat, ...) {
	va_list args;
	FILE * pFile;
	int i = 0;
	
	if (lpAppData->uOpts & OPT_LOG) {
		pFile = fopen(lpAppData->szAppLogFile, "a");
		
		if (pFile) {
			fprintf(pFile, "%s  ", GetTimeStr(lpAppData));
			
			va_start (args, szFormat);
			
			i = vfprintf(pFile, szFormat, args);
			
			if (!strchr(szFormat, '\n')) {
				fputs("\n", pFile);
			}
			
			va_end (args);
			
			fclose(pFile);
		}
		
		if (!i) {
			//Turn off logging!!
			lpAppData->uOpts &= ~OPT_LOG;
			DispError(lpAppData, "Error: cannot write to log file %s", lpAppData->szAppLogFile);
			lpAppData->uError = ERROR_CANT_WRITELOG;
			//return FALSE;
		}
	}
}

void DispError (LPAPPDATA lpAppData, char *szFormat, ...) {
	va_list args;
	FILE * pFile;
	int i = 0;
	
	
	if ( !(lpAppData->uOpts & OPT_QUIET) )  {
		va_start (args, szFormat);
		
		vfprintf(stderr, szFormat, args);
		
		if (!strchr(szFormat, '\n')) {
			fputs("\n", stderr);
		}
		
		va_end (args);
		
	}
	
	if (lpAppData->uOpts & OPT_LOG) {
		
		pFile = fopen(lpAppData->szAppLogFile, "a");
		
		if (pFile) {
			fprintf(pFile, "%s  ", GetTimeStr(lpAppData));
			
			va_start (args, szFormat);
			
			i = vfprintf(pFile, szFormat, args);
			
			if (!strchr(szFormat, '\n')) {
				fputs("\n", pFile);
			}
			
			va_end (args);
			
			fclose(pFile);
		}
		
		if (!i) {
			//Turn off logging!!
			lpAppData->uOpts &= ~OPT_LOG;
			DispError(lpAppData, "Error: cannot write to log file %s", lpAppData->szAppLogFile);
			lpAppData->uError = ERROR_CANT_WRITELOG;
			//return FALSE;
		}
	}
}


void DispMessage (LPAPPDATA lpAppData, const char *szFormat, ...) {
	va_list args;
	FILE * pFile;
	int i = 0;
	
	if ( !(lpAppData->uOpts & OPT_QUIET) )  {
		va_start (args, szFormat);
		
		vfprintf(stdout, szFormat, args);
		
		if (!strchr(szFormat, '\n')) {
			fputs("\n", stdout);
		}
		
		va_end (args);
	}
	
	
	if (lpAppData->uOpts & OPT_LOG) {
		
		pFile = fopen(lpAppData->szAppLogFile, "a");
		
		if (pFile) {
			fprintf(pFile, "%s  ", GetTimeStr(lpAppData));
			
			va_start (args, szFormat);
			
			i = vfprintf(pFile, szFormat, args);
			
			if (!strchr(szFormat, '\n')) {
				fputs("\n", pFile);
			}
			
			va_end (args);
			
			fclose(pFile);
		}
		
		if (!i) {
			//Turn off logging!!
			lpAppData->uOpts &= ~OPT_LOG;
			DispError(lpAppData, "Error: cannot write to log file %s", lpAppData->szAppLogFile);
			lpAppData->uError = ERROR_CANT_WRITELOG;
			//return FALSE;
		}
	}
}

boolean_t FileExists(char *szFilename) {
	
	return ( access( szFilename, F_OK ) != -1 );
	
}


boolean_t GetDefInhibitSecs (LPAPPDATA lpAppData) {
	
	FILE * pFile;
	int	i;
	
	if (!FileExists(lpAppData->szAppDataFile)) {
		lpAppData->uInhibitSeconds = DEF_INHIBIT_TIME;
		if (!PutDefInhibitSecs(lpAppData)) {
			return FALSE;
		}
	}
	
	
	pFile = fopen(lpAppData->szAppDataFile, "r");
	
	if (!pFile) {
		DispError(lpAppData, "Error: cannot read file %s", lpAppData->szAppDataFile);
		lpAppData->uError = ERROR_CANT_READDEFSECS;
		return FALSE;
	}
	
	i = fscanf(pFile, "%u", &lpAppData->uInhibitSeconds);
	fclose(pFile);
	
	return (lpAppData->uInhibitSeconds > 0);
}


boolean_t PutDefInhibitSecs (LPAPPDATA lpAppData) {
	FILE * pFile;
	int i = 0;
	
	// have to have a valid value..
	if (!lpAppData->uInhibitSeconds > 0)
		return FALSE;
	
	pFile = fopen(lpAppData->szAppDataFile, "w");
	
	if (pFile) {
		i = fprintf(pFile, "%u", lpAppData->uInhibitSeconds);
		fclose(pFile);
	}
	
	if (!i) {
		DispError(lpAppData, "Error: cannot write file %s", lpAppData->szAppDataFile);
		lpAppData->uError = ERROR_CANT_SAVEDEFSECS;
		return FALSE;
	}
	
	return TRUE;
}



// http://stackoverflow.com/questions/799679/programatically-retrieving-the-absolute-path-of-an-os-x-command-line-app

boolean_t GetAppPath(char *lpAppPath, const char *lpargvzero) {

	if (realpath(lpargvzero, lpAppPath) != NULL)
		return TRUE;
	
	return FALSE;
}


boolean_t GetOpts (LPAPPDATA lpAppData, int argc, const char *argv[]) {
	
	char *lpPos;
	int n, nRet;
	
	
	//Zero the app data structure..
	bzero(lpAppData, sizeof(APPDATA));
	
	strcpy(lpAppData->szAppVersion, APP_VERSION);
	
	if (!GetAppPath(lpAppData->szAppPath, argv[0])) {
		strcpy(lpAppData->szAppPath, argv[0]);
	}
	
	lpPos = strrchr(lpAppData->szAppPath, '/');
	if (lpPos) {
		strcpy(lpAppData->szAppName, ++lpPos);
	} else {
		strcpy(lpAppData->szAppName, argv[0]);
	}
	
	strcpy(lpAppData->szAppID, lpAppData->szAppName);
	strcat(lpAppData->szAppID, "-");
	strcat(lpAppData->szAppID, getenv("USER"));
	
	strcpy(lpAppData->szAppDataFile, getenv("HOME") );
	
	strcat(lpAppData->szAppDataFile, "/.");
	strcat(lpAppData->szAppDataFile, lpAppData->szAppName);
	
	//Set option defaults..
	lpAppData->uOpts |= ACT_INHIBIT;
	
	//Process the cmd line args..
	for (n = 1; n < argc; n++) {
		
		if ( (argv[n][0] == '-' && argv[n][1] != '-') || argv[n][0] == '/') {
			switch (tolower(argv[n][1])) {
				case 'h':
					lpAppData->uOpts |= ACT_HELP;
					lpAppData->uOpts &= ~ACT_INHIBIT;
					break;
				case 'v':
					lpAppData->uOpts |= ACT_VERSION;
					lpAppData->uOpts &= ~ACT_INHIBIT;
					break;
				case 't':
					if (n+1 < argc && *argv[n+1] != '-') {
						n++;
						if (strcasestr(argv[n], "i"))
							lpAppData->uInhibitFlags |= NO_IDLE_SLEEP;
						if (strcasestr(argv[n], "d"))
							lpAppData->uInhibitFlags |= NO_DISP_SLEEP;
					}
					break;
					
				case 'q':
					lpAppData->uOpts |= OPT_QUIET;
					//lpAppData->uOpts &= ~OPT_DEBUG;
					break;
				case 'd':
					lpAppData->uOpts |= OPT_DEBUG;
					//lpAppData->uOpts &= ~OPT_QUIET;
					break;
				case 's':
					lpAppData->uOpts |= OPT_SAVE;
					break;
				case 'l':
					lpAppData->uOpts |= OPT_LOG;
					if (n+1 < argc && *argv[n+1] != '-') {
						strcpy(lpAppData->szAppLogFile, argv[++n]);
					}
					break;
				case 'r':
					if (n+1 < argc && *argv[n+1] != '-') {
						strcpy(lpAppData->szAppReason, argv[++n]);
					}
					break;
				case 'u':
					lpAppData->uOpts |= OPT_UTC;
					break;
			}
		} else if (!strcasecmp(argv[n], "--help")) {
			lpAppData->uOpts |= ACT_HELP;
			lpAppData->uOpts &= ~ACT_INHIBIT;
		} else if (!strcasecmp(argv[n], "--version")) {
			lpAppData->uOpts |= ACT_VERSION;
			lpAppData->uOpts &= ~ACT_INHIBIT;
		} else if (!strcasecmp(argv[n], "--type")) {
			if (n+1 < argc && *argv[n+1] != '-') {
				n++;
				if (strcasestr(argv[n], "i"))
					lpAppData->uInhibitFlags |= NO_IDLE_SLEEP;
				if (strcasestr(argv[n], "d"))
					lpAppData->uInhibitFlags |= NO_DISP_SLEEP;
			}
			
		} else if (!strcasecmp(argv[n], "--display")) {
			lpAppData->uInhibitFlags = NO_DISP_SLEEP;
		} else if (!strcasecmp(argv[n], "--quiet")) {
			lpAppData->uOpts |= OPT_QUIET;
		} else if (!strcasecmp(argv[n], "--debug")) {
			lpAppData->uOpts |= OPT_DEBUG;
		} else if (!strcasecmp(argv[n], "--save")) {
			lpAppData->uOpts |= OPT_SAVE;
		} else if (!strcasecmp(argv[n], "--utc")) {
			lpAppData->uOpts |= OPT_UTC;
		} else if (!strcasecmp(argv[n], "--gmt")) {
			lpAppData->uOpts |= OPT_UTC;
		} else if (!strcasecmp(argv[n], "--log")) {
			lpAppData->uOpts |= OPT_LOG;
			if (n+1 < argc && *argv[n+1] != '-') {
				strcpy(lpAppData->szAppLogFile, argv[++n]);
			}
		} else if (!strcasecmp(argv[n], "--reason")) {
			if (n+1 < argc && *argv[n+1] != '-') {
				strcpy(lpAppData->szAppReason, argv[++n]);
			}
		} else {
			//This must be the inhibit time
			//Only get this ONCE!
			if ( !lpAppData->uInhibitSeconds ) {
				lpAppData->cInhibitUnits = 's';
				nRet = sscanf(argv[n], "%f%c", &lpAppData->fInhibitDuration, &lpAppData->cInhibitUnits);
				if (nRet != EOF && nRet > 0) {
					switch (tolower(lpAppData->cInhibitUnits)) {
						case 'm':
							lpAppData->fInhibitDuration *= 60;
							break;
						case 'h':
							lpAppData->fInhibitDuration *= 3600;
							break;
						case 'd':
							lpAppData->fInhibitDuration *= 86400;
							break;
					}
					
					//No such thing as negative time in this continuum..
					if (lpAppData->fInhibitDuration < 0.0)
						lpAppData->fInhibitDuration = -(lpAppData->fInhibitDuration);
					
					lpAppData->uInhibitSeconds = (uint) (lpAppData->fInhibitDuration + 0.5);
				}
				
			}
		}
		
	}
	
	//Default the reason...
	if (!*lpAppData->szAppReason) {
		strcpy(lpAppData->szAppReason, DEF_REASON);
	}
	
	if (!*lpAppData->szAppLogFile) {
		sprintf(lpAppData->szAppLogFile, "/var/log/%s.log", lpAppData->szAppName);
	}
	
	//Default the reason...
	if (!*lpAppData->szAppDispEnv) {
		strcpy(lpAppData->szAppDispEnv, DEF_DISPLAY);
	}
	
	
	if ( !lpAppData->uInhibitSeconds && !GetDefInhibitSecs(lpAppData) ) {
		lpAppData->uInhibitSeconds = DEF_INHIBIT_TIME;
	}
	
	if ( !lpAppData->uInhibitFlags ) {
		lpAppData->uInhibitFlags = NO_IDLE_SLEEP;
	}
	
	
	//Show the app data...
	if (lpAppData->uOpts & OPT_DEBUG) {
		
		DispError (lpAppData, "    AppName == %s", 		lpAppData->szAppName);
		DispError (lpAppData, "      AppID == %s", 		lpAppData->szAppID);
		DispError (lpAppData, "    AppPath == %s", 		lpAppData->szAppPath);
		DispError (lpAppData, "    AppData == %s", 		lpAppData->szAppDataFile);
		DispError (lpAppData, "     AppLog == %s", 		lpAppData->szAppLogFile);
		DispError (lpAppData, "  AppReason == %s", 		lpAppData->szAppReason);
		DispError (lpAppData, "    AppOpts == 0x%08x",	lpAppData->uOpts);
		DispError (lpAppData, "InhibitSecs == %d", 		lpAppData->uInhibitSeconds);
		DispError (lpAppData, "Display env == %s", 		lpAppData->szAppDispEnv);
	}
	
	if (lpAppData->uOpts & OPT_SAVE) {
		PutDefInhibitSecs(lpAppData);
	}
	
	//Set the DISPLAY environmental variable so that the app can connect to dbus..
	setenv("DISPLAY", lpAppData->szAppDispEnv, 1);
	
	return TRUE;
}

//void MyTimerCallback(CFRunLoopTimerRef timer, CFRunLoopTimerContext *lpRunLoopTimerContext) {
void MyTimerCallback(CFRunLoopTimerRef timer, void *info) {
	LPAPPDATA lpAppData;
	
	//lpAppData = (LPAPPDATA) lpRunLoopTimerContext->info;
	lpAppData = (LPAPPDATA) info;
	
	CFRunLoopRef refLoop;
	
	
	refLoop = CFRunLoopGetMain () ;
	
	if (lpAppData->uOpts & OPT_DEBUG) {
		DispMessage(lpAppData, "Stopping RunLoop 0x%08x..", refLoop );
	}
	
	CFRunLoopStop ( refLoop );
}



int main (int argc, const char * argv[]) {
	APPDATA AppData;
	int iAction;
	CFRunLoopTimerRef timer;
	CFRunLoopTimerContext context = { 0, &AppData, NULL, NULL, NULL };
	//IOPMAssertionID assertionID;
	IOReturn success;
	
	//Parse the cmd line args..
	if (!GetOpts(&AppData, argc, argv)) {
		DispSyntax(&AppData);
		exit(1);
	}
	
	//Perform the action..
	iAction = AppData.uOpts & ACT_ACTIONMASK;

	switch (iAction) {
		case ACT_INHIBIT:

			timer = CFRunLoopTimerCreate(NULL, CFAbsoluteTimeGetCurrent() + AppData.uInhibitSeconds, 0, 0, 0, MyTimerCallback, &context);
			
			if (timer != NULL); {
				CFRunLoopAddTimer(CFRunLoopGetCurrent(), timer, kCFRunLoopCommonModes);

				//Inhibit sleep..
				success = IOPMAssertionCreate( ( AppData.uInhibitFlags & NO_DISP_SLEEP ? kIOPMAssertionTypeNoDisplaySleep : kIOPMAssertionTypeNoIdleSleep ),
																kIOPMAssertionLevelOn, &AppData.uInhibitCookie); 
				
				if (success == kIOReturnSuccess) {
					
					if (AppData.uOpts & OPT_DEBUG) {
						DispMessage(&AppData, "%s 0x%08x granted for %u seconds, reason: %s..", ( AppData.uInhibitFlags & NO_DISP_SLEEP ? "kIOPMAssertionTypeNoDisplaySleep" : "kIOPMAssertionTypeNoIdleSleep" ),
									AppData.uInhibitCookie, AppData.uInhibitSeconds, AppData.szAppReason );
					} else {
						DispMessage(&AppData, "Inhibiting for %u seconds..", AppData.uInhibitSeconds);
					}
					
				
					CFRunLoopRun();
				
					if (AppData.uOpts & OPT_DEBUG) {
						DispMessage(&AppData, "UnInhibiting %s 0x%08x ", ( AppData.uInhibitFlags & NO_DISP_SLEEP ? "kIOPMAssertionTypeNoDisplaySleep" : "kIOPMAssertionTypeNoIdleSleep" ), AppData.uInhibitCookie);
					} else {
						DispMessage(&AppData, "Uninhibiting..");
					}
					
					//Uninhibit system idle sleep..
					success = IOPMAssertionRelease(AppData.uInhibitCookie);
				}

			}
			
			
			CFRunLoopTimerInvalidate(timer);
			
			CFRelease(timer);
			
			break;
			
		case ACT_HELP:
			DispSyntax(&AppData);
			break;
			
		case ACT_VERSION:
			DispVersion(&AppData);
			break;
			
	}
	
	
	return AppData.uError;
	
}

